Skip to content

Upstream sync 11E: AGC driver onto Metal (29 commits) - #512

Merged
RINNECODER merged 31 commits into
mainfrom
sync/upstream-11-agc
Oct 15, 2026
Merged

RINNECODER merged 31 commits into
mainfrom
sync/upstream-11-agc

Conversation

@RINNECODER

@RINNECODER RINNECODER commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Upstream sync 11, slice E: AGC driver onto Metal. Range 63fb0cd92..upstream/main (head d92a8dad3), area agc (124 listed commits), plus the four shader-recompiler commits #511 left for this slice. It was built on #511, whose request-serializer numbering it extends (fork version 25), and was rebased onto main after #511 and #507 merged. Both rebases were clean, and the tree is unchanged from the tested pre-rebase tip. 29 upstream commits are cherry-picked with -x, one each (28 from the AGC list, 1 shader leftover). Two fork commits follow (one forced by a shared API change, one test fixture). Six commit messages were reworded because they named a title or a title ID (f4f69bb71, 11ef8e6c8, fc2425348, 67973cd89, 6ad9372fd, 72518f64f); the title is now "a shipping title" / "an Unreal Engine 5 title". One added debt line (d16745be1) had a title ID replaced by "one title", and the sceAgcInit debt line ported from eb1b9eca9 leaves out two homebrew title IDs.

Versions: the recompile-request serializer goes from fork version 25 to fork version 26 (adds quadPixelMask, written after the fork's layerBase). Upstream numbers this field 28. Upstream's second bump (29, quad sample masks) belongs to 1ffc5f672, which is skipped, so there is no fork 27. ShaderDiskCache::FormatVersion stays 37: the stencil-replay series that changes the cache payload is skipped (see below).

Metal backend files: MetalDriver.mm, MetalDraw.mm, MetalPacketExecution.mm and MetalTexture* are untouched. One Metal-side file changed: Graphics/Metal/MetalHostExports.cpp (fork commit 3dc0eea15), because 11ef8e6c8 changed the signature of AgcDriverBeginShaderPreparation_nid_postfix. The empty Metal stub now takes the stage list and still does nothing.

Ported

On the Metal/Rosetta path (shared State.cpp, ShaderInputState.cpp, libSceAgc, libc, or the shader recompiler):

  • 72518f64f single-sample sample masks as a per-quad-pixel discard. Shared decode now accepts PA_SC_AA_MASK on single-sample draws, and the pixel stage info carries the 4-bit quad mask. The recompiler kills uncovered quad pixels; on Metal that reaches MSL through the SPIR-V bridge. Conflicts: the fork's serializer layout (layerBase at v24, linked-call fallbacks at v25) is kept, and the mask is read at v26. The ShaderMemory test moves its fixtures to 26/27, and the legacy replays check the default mask 0xf. The message no longer names a title, and its version note says fork 26.
  • 90b20afac demote quad-mask pixels to helpers. The recompiler emits OpDemoteToHelperInvocation only when the target lists that capability. The Metal driver's target does not, so on Metal a partial quad mask keeps OpKill, and a pixel program that reads neighbour lanes under one is refused with a named reason. Enabling demote on Metal (MSL 2.3) is left for a Metal-side PR. Vulkan side: the fork's ValidateShaders/Inspect gain only the demote flag, not upstream's clip/cull/viewport-layer/descriptor-array flags.
  • 90783f274 (shader-slice leftover) whole-quad mode counts as a neighbour-lane read. dbbe282c1 ReadFirstLane/Ballot/Permute count too, and the new ShaderMemory test is registered.
  • 843f7e963 skip unused pixel stages with retained export formats. PixelProgramSkipped is used by the shared DrawDispatch.cpp on Metal. Test conflicts: only this commit's tests were added.
  • ee1e34197 an unregistered compute shader takes its float mode from the live COMPUTE_PGM_RSRC1. DispatchFloatMode is built on the fork's shared FloatModeRegisterFor/DecodeFloatMode instead of upstream's switch, and the fork's wave-size decode is kept.
  • 11ef8e6c8 settle registration preparations before a preparation scope locks (libSceAgc call sites plus the Vulkan registry; see the fork stub above).
  • eb1b9eca9 sceAgcInit returns 0 for versions >= 14 without a table lookup. 9d412df7b and 4832bd825 were already on main. The debt line goes into TechnicalDebt.md (main has no debt/libSceAgc.md).
  • facc03ae9 build with libc++ (AtomicSharedPtr, libc GuestAllocations, SaveData file clock).
  • 93ab13719 prebuilt shader pack loading tests (follows f31deebcd from Upstream sync 11D: shader recompiler (13 commits) #511). Conflict: only this commit's tests were added, beside main's plan round-trip test.
  • Debt notes: 58b224502 (type-0 descriptors; the behaviour was already on main), 5d9def64e (COPY_DATA from a register; the code was already on main), d16745be1 (accepted VGT_SHADER_STAGES_EN).

Vulkan driver side, carried to stay close to upstream (not built on macOS):

  • f4f69bb71 return freed memory when shader preparation drains (keeps the fork's std::function task queue)
  • 0b5a4aae3 store small pending storage results at each flip (header: declarations from unported neighbours dropped)
  • dc39e051b, 7bfeeb977, ca73d0fb3 write-back layer advance and settle tests; 6ad9372fd overhanging last block row
  • 59235d9cb v_cmp denormals test compiles once
  • fc2425348, fa4fc2347 stream copy kernel HLE and tests
  • db1ddb96c adjacent registered allocations cover a write-back (test: this commit's case only)
  • 67973cd89 a live-backed snapshot mismatch no longer fails the dispatch
  • ea13f6a4b, 6e3152f0e print the first worker failure. Only PrintFailure is added, because the fork's Lifecycle.cpp has no import-stack helpers. The test list drops upstream's present-mode test, which main lacks.
  • 64ffeb8c9 size the storage image cache from the device memory budget
  • 9960503f6, 7af296130 name the shader when a draw stage fails (the fork keeps no stageCapture.capture)

Skipped

Fork commits

  • 3dc0eea15 fix(agc): take the stages in the Metal shader preparation stub (MetalHostExports.cpp, forced by 11ef8e6c8).
  • e50cf0545 test(agc): the Metal render-state early-depth fixture sets full PA_SC_AA_MASK. 72518f64f reads those registers in DecodePixelStageInfo, so anyps5_metal_render_state threw "missing register at DWORD 0x30e" without them.

Evidence

  • Rosetta x86_64 (agent_build.sh rosetta, -DANYPS5_ROSETTA_LIBS=ON, BUILD_TESTING=ON): rosetta_libs rosetta_engine anyps5_agc_replay libSceAgcDriver metal_utility_tests built. ctest metal_utility_tests passed. rosetta_direct_graphics_tests does not compile (ShaderMemory.hpp:4 includes Recompiler.hpp, which is not on the target's include path). Main has the same include and the same RosettaGraphics.cmake/test sources, so this is pre-existing and not from this slice. build-rosetta is kept, so the coordinator can replay with build-rosetta/rosetta/bin/anyps5_agc_replay.

  • arm64 guest replay (agent_build.sh guest-replay): full build, then ctest without anyps5_metal_presentation_replay: 59/59 passed after fork commit e50cf0545 (58/59 before it).

  • request_serializer_tests (core/shader/recompiler/tests/RequestSerializerTests.cpp with RequestSerializer.cpp, built standalone with clang++ arm64): passed at fork version 26.

  • New execution tests on the Metal device shim: QuadPixelMask and StreamCopy were tried in tests/metal_semantics/registration.cmake. They need VulkanDevice graphics members (PixelPushLayout, CompiledShader) or Vulkan fill HLE that the shim lacks, so they are not registered.

  • Vulkan-device targets and the new tests/execution/* files are carried but not compiled or run on macOS.

  • After the rebase onto main (2c3ff5018), rosetta (rosetta_libs rosetta_engine anyps5_agc_replay libSceAgcDriver metal_utility_tests) and the full guest-replay config were rebuilt. Guest-replay ctest passed 59/59 without anyps5_metal_presentation_replay, and metal_utility_tests passed 1/1.

  • AGC frame replay (x86_64 anyps5_agc_replay). The baseline is main after Upstream sync 11D: shader recompiler (13 commits) #511, whose tree equals this branch's base:

    check main (after Upstream sync 11D: shader recompiler (13 commits) #511) this branch
    live map --expect-hash 5e5670079f1ae58a (--repeat 4) match, stable match, stable (run 1)
    menu --expect-png replay-ref.png --max-diff-pixels 8 (--repeat 3) 1 px differ: match 2 px and 3 px differ in two runs: match
    live map worker M instructions, median 4402 4859
    menu worker M instructions, median 1209 1301 / 1828
    • In both branch runs the menu hash was reported unstable across repeats, a 1-pixel flicker on the 3D character. That is the documented menu flicker, so the tool exits 1 there. The PNG check is the gate for this frame, and it passed.
    • Three later live-map reruns hit the 5-minute timeout before printing anything. A main build run at the same time hit the same timeout (load average 19-28 from other agents' compiles), so the timeouts come from load, not from this branch.
    • The instruction numbers are indicative only. Each is a single short run, made without the exclusive replay lock on a loaded machine. The live-map +10% should be remeasured under the lock before anyone reads it as a regression. No performance claims.
  • Not run: game smokes.

  • No performance claims; nothing was measured for speed.

Review findings (chatgpt-codex-connector)

Reviewed at e50cf05, the pre-rebase tip with the same tree. No review of the rebased head arrived within 15 minutes.

sev location finding action
P2 core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp:1291 a wave64 ds_ordered_count whose first active lane is in the upper half shuffles its amount from the lower half Not fixed: P2, in #511 code carried as upstream wrote it; recorded as a followup

🤖 Generated with Claude Code

https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c01a17cd-94e8-4204-a70c-8555c2aaca46
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T06:19:37.719618Z e50cf05 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e50cf05454

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

auto amount = count;
if (ordered) {
amount = state.module.AllocateId();
state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), amount, ConstantU32(state, spv::ScopeSubgroup), ctx.Arg(inst, 4), EmitHostSubgroupLane(state, sourceLane));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Shuffle the ordered amount across both wave64 halves

For a wave64 ds_ordered_count where lanes 0–31 are inactive and the first active lane is in the upper half, first identifies that upper lane but sourceLane masks it to 0–31 and the shuffle reads ctx.Arg(inst, 4) from the lower-half context. Because the second half returns before this code, the atomic increment uses the inactive lower counterpart's value instead of the first active lane's amount, corrupting the GDS counter when those values differ. Use the paired-half-aware shuffle path (such as ctx.Shuffle(inst, 4, first)) rather than shuffling only the current half.

Useful? React with 👍 / 👎.

milescoviello and others added 26 commits October 15, 2026 02:45
A shipping title ends its first run, with no resource
plans in the shader cache, with about 4 GB more resident memory than a
run with a warm cache. The threads of the registration prepare pool
free what they allocate to prepare a shader, but glibc keeps the freed
memory in their arenas: at 110 s, 4.6 GB of the 6.5 GB the arenas held
was free.

When the pool has no task left, the worker that finished last calls
malloc_trim, at most once a second, which gives the free pages of every
arena back to the system.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f4f69bb)
…ive COMPUTE_PGM_RSRC1

(cherry picked from commit ee1e341)
…locks

On a host-imported surface a storage write-back keeps the 64 KiB blocks
the CPU wrote since their tracked layer's generation, stores the rest
and stamps the stored ranges with MarkWritten. Settling advanced the
stored layers' generations past those stamps only when no layer kept a
block (or when an adjacent pending surface needed the collect). When one
tracked layer kept a block, every other stored layer kept its old
generation, so the write-back's own stamps read as CPU writes: the next
write-back kept those layers as well and did not store the GPU's newer
results over them.

A tracked layer is one 64 KiB unit when the surface has block units:
tiled (not linear), 64 KiB-aligned base, not thick, with layer and block
tracking on. Otherwise it is an array layer (64 KiB-aligned layer
slices, layer tracking on) or the whole surface.

Settling now collects and advances each stored layer that kept no
block; a layer that kept a block keeps its old generation and
re-uploads at its next use, as before. When no stored layer advances
(nothing stored, or every selected layer kept a block, such as a single
tracked layer that kept one), no adjacent pending surface needs the collect
and no unit was retiled into the unit shadow, settling still skips the
collect, as main did. Units retiled into the unit shadow are now marked
at the settle collect's generation also when a block was kept; main
marked them at TrackerGeneration() there without collecting. The two
differ only for a CPU write to a stored unit made after the keep
decision: main left it to a later walk, which made the retiled unit
stale so the CPU's bytes won; now the walk stamps it at the marked
generation and the stored results win, as they already did when no
block was kept.

agc_storage_kept_unit_settle uses a 1024x512 RGBA8 surface in 64 KiB
tiling (32 block units): it writes one byte of unit 0 from the CPU,
stores a red clear, stores a blue clear, and requires units 1-31 to
hold blue and unit 0 to keep the CPU's byte after both stores. On main
the GPU-mapped direct memory case fails with unit 1 still red. The
watched-memory case (host imports off: the write-back that stores
through the CPU) passes on main; it guards the kept layer's old
generation and fails when settling also advances a layer that kept a
block. The test returns 77, naming the reason, when the direct-memory
case cannot run: no write watching, no host imports, direct memory not
write-watched, or its host import refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit dc39e05)
…ompared

agc_storage_kept_unit_settle checked that the GPU-mapped direct memory
was write-watched before it made the host import. On Linux, when host
imports resolve write protection and dma-buf imports keep it, the
host-pointer import is tried first in a window that does not stay
watched, and dma-buf only when it fails (importAllocation in
GuestBufferMemory.cpp). Where the host-pointer import of shared memory
succeeds, as on Intel's ANV, the import takes the direct memory out of
the write watch, so the case ran on the compared path: the upload saves
the guest bytes, the first write-back keeps unit 0 by comparison and
drops the saved bytes of every unit it selected, and the second
write-back has nothing to compare. Before e544993 it stored every
unit whole, over the CPU's byte in unit 0, which is the failure
reported on Linux; on main it throws (technical debt: results over
memory outside the write watch without saved bytes). Neither depends
on the settle change. On Windows, importing watched private memory
(compared there) reproduces both: on 7e181fc, with or without this
branch's Texture.cpp change, the second check fails with that message;
on main the second write-back throws.

The case now checks the write watch again after the import and returns
77, naming the reason, when the import took the memory out of it. Where
the import keeps the memory watched (the Windows alias import, a Linux
dma-buf import) it runs as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7bfeeb9)
…ilable

Co-Authored-By: Codex <noreply@openai.com>
(cherry picked from commit ca73d0f)
Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 58b2245)
… locks

sceAgcCreateInterpolantMapping resolved the pixel shader's ABI, which takes
the process-wide shader preparation lock until the scope ends, and then
waited under that lock for the vertex shader's background registration
preparation. sceAgcCreatePrimState and sceAgcLinkShaders could wait the same
way. Every other shader registration and ABI resolution queued behind that
one compile: on a shipping title the lock was held 8 to 10 s of every 10 s, almost
all of it waiting for registration preparations, while its loader threads
waited 13 to 45 s per 10 s to take it.

A preparation scope now names the shaders it resolves and waits for their
registration preparations before it can take the lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 11ef8e6)
…location

A storage image whose surface runs past the end of its registered
allocation had every write-back dropped as "memory no longer
registered". Titles can size a single-level surface's allocation as
pitch x height. A tiled mip's 64 KiB blocks are stored row-major, so
that size ends inside the last block row, and the bytes after it are
the tail of that row: they hold texels with y < height, which the GPU's
own store writes into the next allocation. When the allocation ends
inside the last block row and registered readable memory continues to
the surface's end, the write-back now stores the whole surface, as the
GPU does. Padding slots keep their bytes, since the store writes only
bytes that changed.

A shipping title streams its lightmaps into an 8160x8160 BC3 pool
(SW_64KB_S, 2048 block rows) through a 2040x2040 RGBA32UI alias written
by compute. The game allocates 2040 rows, so all its write-backs were
dropped and the walls sampled leftover memory as speckled noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 6ad9372)
…put groups

The test recompiled and rebuilt the same program for every group of 32
input pairs. Only the input buffer changes between groups, so compile
once per compare, EXEC/VCC capture and float mode (96 programs instead of
768) and dispatch it for each group.

On lavapipe the test drops from about 11.5 s to 1.3 s; it is the slowest
test of the Linux CI run (14.5 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 59235d9)
Pointer-shaped calls match the boykopovar#4808 console probe (success, no writes); selecting table 13 for those values was unsupported.

(cherry picked from commit eb1b9ec)
A 16-word compute kernel copies a buffer in 16-byte elements: 64 threads a
group, thread t of group g loads elements g*128+t and g*128+64+t from the
index-addressed buffer in s[0:3] and stores them at the same indices of
s[4:7]. Run as a shader against host imports it costs 11.8 ms of GPU per
present in a shipping title's main menu on an RTX 4060.

Matched like the existing copy kernel, by its code hash and its descriptors
(16-byte stride and no swizzle on both, the same word 3), it becomes one copy
through the existing copy HLE path: the elements the kernel stores, min of
groups * 128 and the destination's record count, when the source's record
count covers them all. APS5_NO_COPY_HLE turns it off with the other kernel.

(cherry picked from commit fc24253)
The stream copy kernel is matched by its 16 code words instead of a hash, and the
match and its range checks become free functions (MatchesStreamCopyKernel,
StreamCopyRange) so a test can reach them. Its loads and stores are format
instructions, so the match also requires the descriptor the pattern fill takes:
32_32_32_32_UINT, XYZW selects, no add-tid, out-of-bounds mode 0, type 0. Copy
paths are counted in copyPathCounts whether or not APS5_PROFILE_DRAW is set.

agc_driver_stream_copy checks the match against every word with a bit flipped,
the descriptor and dispatch refusals, the range clamp and refusals, and runs nine
dispatches through the driver: four must take the HLE path and copy elements
g*128+t and g*128+64+t, five must be refused for their shape and leave the
shader's result.

(cherry picked from commit fa4fc23)
…-image write-back

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit db1ddb9)
Build packs from EncodeEntry output and check the hit and miss paths,
an entry stored under another key, a truncated entry, a damaged index,
an out-of-range record, a foreign compiler source version and an
unknown format. A child process covers pack discovery on a real disk
load, the ANYPS5_NO_PREBUILT_SHADERS opt out and fall-through to the
writable cache. ARCHITECTURE.md describes where the prebuilt lookup
sits.

(cherry picked from commit 93ab137)
…rs it reads

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 5d9def6)
…e dispatch

A captured word that no longer matches a live import or mirror only means the
capture raced a guest write; the GPU reads the live bytes either way, as the
hardware would. The compare now counts such mismatches and reports them with
the snapshot, the region and the first differing offset at most every 10 s
instead of throwing; APS5_SNAPSHOT_CHECK_FATAL=1 restores the throw, and a
snapshot-backed region still fails, now naming the snapshot, the region and
the first differing offset. an Unreal Engine 5 title hit the throw
within 65 to 91 s of reaching its title screen.

(cherry picked from commit 67973cd)
Apple clang ships libc++, which differs from libstdc++ where the sources
relied on it:

- std::atomic<std::shared_ptr<T>> is not provided. AtomicSharedPtr is that
  type where the library has it and a mutex-guarded pointer with the same
  member functions elsewhere; the AGC driver uses it.
- std::atomic_ref<const T> is not provided; the page span reads go through
  a non-const reference.
- std::filesystem::file_time_type::clock::to_sys is not provided;
  std::chrono::file_clock::to_sys is.
- std::max and std::min do not deduce across the integer types of the
  guest range arithmetic, which is spelled with an explicit type.
- <string> and <initializer_list> are included where the sources and tests
  use them.

(cherry picked from commit facc03a)
…ted memory budget

The storage image cache kept a quarter of the device-local heap (4 GiB on a
16 GiB card) regardless of how much of the heap the driver reports as usable.
With a desktop and other allocations the reported budget is about 10 GiB, the
cache stayed full at 4 GiB, and after minutes of a cutscene device buffer
allocations failed with VK_ERROR_OUT_OF_DEVICE_MEMORY. The cache budget is now
the smaller of that quarter of the heap and a quarter of the reported budget,
at least 512 MiB, read once per second; APS5_TEXTURE_CACHE_MIB still overrides
it. The budget report names the storage budget in use. A test covers the limits.

(cherry picked from commit 64ffeb8)
With one sample, PA_SC_AA_MASK leaves one coverage bit per pixel of the 2x2 quad,
so a draw can cover only some pixels of every quad (a shipping title renders a
target one quarter of the pixels at a time this way). The pixel stage info carries that 4-bit
mask; a fragment shader whose mask is not full starts by killing the invocation
unless its gl_FragCoord quad position is covered, and runs without early fragment
tests. The mask is part of the recompile cache key and the serialized request
(fork request version 26). Sample masks of multisampled draws stay refused.

(cherry picked from commit 72518f6)
RINNECODER and others added 5 commits October 15, 2026 02:45
11ef8e6 passes the stages a preparation scope covers to
AgcDriverBeginShaderPreparation_nid_postfix, so the Vulkan driver can
settle their queued preparations. The Metal build's empty stub takes the
same arguments and still keeps no prepared state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
With PA_SC_AA_MASK covering only some pixels of a quad, the uncovered pixels
run on the hardware as helper lanes, so derivatives of the covered pixels stay
defined. OpKill ends those invocations; OpDemoteToHelperInvocation keeps them
as helpers. The device enables VK_EXT_shader_demote_to_helper_invocation when
available and passes the DemoteToHelperInvocation capability and its SPIR-V
extension to the recompiler target and the graphics shader validation. A
demoted pixel also reads as a helper, so its initial EXEC bit is clear as on the
hardware. Without demote, OpKill stays for pixel programs that read no
neighbouring lanes; implicit-LOD sampling, LOD queries and cross-lane reads are
refused. The quad pixel mask execution test now also samples a mipmapped
texture with implicit LOD under the mask.

(cherry picked from commit 90b20af)
…ask without demote

A program that enables whole-quad mode (s_wqm_b64) and then reads a ballot or
readfirstlane sees the helper lanes, which OpKill removes.

(cherry picked from commit 90783f2)
ReadFirstLane, Ballot and PermuteU32 can consume uncovered helper lanes after a fragment explicitly enables EXEC. Classify those operations as neighbour reads so a partial quad mask refuses their kill fallback when demote is unavailable. ReadFirstLane also stays protected when a constant whole-quad instruction folds away.

The CPU regression covers explicit EXEC, constant WQM, a comparison ballot and ds_permute. Unmasked programs still compile, supported masked programs retain demote and their subgroup operation, and constant colour keeps the kill fallback. The recovered source fails the readfirstlane and ballot checks; the corrected source passes both the focused check and shader-memory suite.

Co-Authored-By: Codex <noreply@openai.com>
(cherry picked from commit dbbe282)
72518f6 reads PA_SC_AA_MASK (0x30e, 0x30f) when it decodes the pixel
stage, for the quad pixel mask. The Metal render-state test built its
registers without them, so the decode threw. The fixture now sets both
masks to cover every sample, as CLEAR_STATE does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj
@RINNECODER
RINNECODER force-pushed the sync/upstream-11-agc branch from e50cf05 to 1ca8a79 Compare October 15, 2026 08:13
@RINNECODER
RINNECODER merged commit 3938f59 into main Oct 15, 2026
1 check passed
@RINNECODER
RINNECODER deleted the sync/upstream-11-agc branch October 15, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.